Introduction
Leaflet is one of the most popular open-source JavaScript libraries
for interactive maps. It’s used by websites ranging from The New York
Times and The Washington Post to GitHub and Flickr, as well as GIS
specialists like OpenStreetMap, Mapbox, and CartoDB.
This R package makes it easy to integrate and control Leaflet maps in
R.
library(tidyverse) # for data cleaning and plotting
library(gplots) # for col2hex() function
library(RColorBrewer) # for color palettes
library(sf) # for working with spatial data
library(leaflet) # for highly customizable mapping
library(carData) # for Minneapolis police stops data
library(tidytuesdayR) # for bigfoot data
library(ggthemes) # for more themes (including theme_map())
library(htmltools)
theme_set(theme_minimal())
Features
Interactive panning/zooming Compose maps using arbitrary
combinations of: ·Map tiles ·Markers ·Polygons ·Lines ·Popups ·GeoJSON
Create maps right from the R console or RStudio Embed maps in
knitr/R Markdown documents and Shiny apps Easily render spatial
objects from the sp or sf packages, or data frames with
latitude/longitude columns Use map bounds and mouse events to drive
Shiny logic Display maps in non spherical mercator projections
*Augment map features using chosen plugins from leaflet plugins
repository
Base maps
We are gonna start from the bottom layer up. The first thing to look
it is the base maps. This means, what will be behind your points or
shapes. There are many options and they are super easy to load in. We
can start off with the basic basic map, simple blue ocean and white
land, if you zoom in there will be more detials
leaflet() %>%
addTiles()
We can also start the map zoomed in on a specific area using latitude
and longitude and the setView() function
Here is Saint Paul MN, now look up your hometown and practice putting
in the coordinates.
leaflet() %>%
addTiles() %>%
setView(lng = -93.093124, lat = 44.949642, zoom = 12)
hometown <- leaflet() %>%
setView(lng = -93.093124, lat = 44.949642, zoom = 12) %>%
addTiles()
There are many other base maps you can use in leaflet! To get
different base maps use the function addProviderTiles(), you will need
to know the name of the basemap Here are some examples:
hometown %>%
addProviderTiles(providers$CartoDB.Positron)
hometown %>%
addProviderTiles(providers$Stamen.Watercolor)
hometown %>%
addProviderTiles(providers$CartoDB.DarkMatterNoLabels)
To get the full list of base maps available through this function
click here: http://leaflet-extras.github.io/leaflet-providers/preview/index.html
If you want to use any other ones from this website just replace what
is after the $ in the addProviderTiles().
Let say you really like the water color base map but there aren’t any
labels, you can add layers to your base maps. Here I have layered the
water color with the light grey base map that had labels. As long as you
set the one on top to have a lower opacity you can see both!
hometown %>% addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5))
Try experimenting with the opacity and different layering techniques
with base maps that you like!
#hometown %>% addProviderTiles(providers$__________) %>%
#addProviderTiles(providers$______,
#options = providerTileOptions(opacity = ___))
Markers
Now we have our base map figured out we can add markers. To do that,
we will needed data. The best data to use is data where the observations
should have a latitude and longitude variable. This makes for a smooth
process when adding markers.
tuesdata <- tidytuesdayR::tt_load('2022-09-13') #Loading in data from a tidy tuesday that has geographical points
##
## Downloading file 1 of 1: `bigfoot.csv`
bigfoot <- tuesdata$bigfoot
First lets practice adding all the data.
leaflet(data = bigfoot) %>% addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5)) %>%
addMarkers(~longitude, ~latitude)
If R is running slowly now its because there are so many data points.
It is best to filter out observations that you need before making a
map.
bigfootsub <- bigfoot %>%
filter(state == "California") %>% #Narrowing down to a state, pick any state you want!
filter(season == "Summer") # Also only looking at bigfoot sightings during one season
leaflet(data = bigfootsub) %>% addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5)) %>%
addMarkers(~longitude, ~latitude)
With less points the software runs far smoother. Now we can add some
fun things!
Plain Markers and Popups
Not only can you add labels to these points but with leaflet you can
add popups
leaflet(data = bigfootsub) %>%
addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5)) %>%
addMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details) # label means what will appear when you hover over the point and popup means what will appear when you click on a point
Awesome Markers
Awesome markers allow you to change the color of the marker dependent
on a variable
# After choosing temperature_high to be my variable I am visualizing I am going to filter out any observations that do not have a value for temperature_high
bftemp <- bigfootsub %>%
filter(temperature_high != "NA")
#Then we will need to assign values of temperature_high to colors creating a getColor function
getColor <- function(bftemp) {
sapply(bftemp$temperature_high, function(temperature_high){ # Creating a function using the bftemp data and temperature_high data
if(temperature_high <= 68) { #creating an if else statement to make the marker a certain color based on temperature
"blue"
} else if(temperature_high >= 69) {
"red"
} else {
"green"
} })
}
icons <- awesomeIcons(
icon = 'ios-close',
iconColor = 'pink', #Controls color of middle x
library = 'ion',
markerColor = getColor(bftemp) #Calls the function
)
leaflet(bftemp) %>%
addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5)) %>%
addAwesomeMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details, icon = icons) #make sure to set the icons
Color
Colors in leaflet need to be in “hex” form. Thus, instead of using
color directly as in ggplot, we will use color functions which could
help us create “hex” color. The color function returns a palette
function that can be passed a vector of input values, and it’ll return a
vector of colors in #RRGGBB(AA) format.There are currently three color
functions for dealing with continuous input: colorNumeric, colorBin, and
colorQuantile; and one for categorical input, colorFactor.
# load continuous dataset
data_site <-
"https://www.macalester.edu/~dshuman1/data/112/2014-Q4-Trips-History-Data.rds"
Trips <- readRDS(gzcon(url(data_site)))
Stations<-read_csv("http://www.macalester.edu/~dshuman1/data/112/DC-Stations.csv")
departSta <- Trips %>%
left_join(Stations, by = c("sstation" = "name")) %>%
group_by(lat, long) %>%
summarise(EventsCount = n())
Create Color Palette
To create a color palette(palette function): you call 1) the color
function with colors (palette) you want to use and 2) optionally, the
domain (the specific variable you want to include) that are expected.
The color function (i.e. colorNumeric) returns a function that maps a
variable’s values to colors in the given palette, in one of the examples
below is “Blues”. So, pal() is a function. We can then use that function
inside other functions where you want colors.
Notice that though the domain arguement is optional, it’s important
to provide a non-NULL value for domain if you use a palette function
multiple times across different data so the scaling between data and
colors is consistent.
# Call the color function (colorNumeric) to create a new palette function
pal <- colorNumeric(c("red", "green", "blue"), 1:10)
# Pass the palette function a data vector to get the corresponding colors
pal(c(1,6,9))
## [1] "#FF0000" "#52E74B" "#6854D8"
# create another color palette function with the range of inputs (i.e. domain)
palDomain <- colorNumeric(
palette = "Blues",
domain = departSta$EventsCount)
# Show the corresponding colors
head(palDomain(departSta$EventsCount))
## [1] "#F5FAFE" "#EFF6FC" "#F2F8FD" "#F5F9FE" "#EEF5FC" "#EFF6FC"
Common Parameters
The palette argument specifies the colors to map the data. Here shows
the four most common forms.
- RColorBrewer
- viridis
- RGB or Named the colors
- A function that receives a single value between 0 and 1 and returns
a color
#RColorBrewer
palBre <- colorNumeric(
palette = "RdYlBu",
domain = departSta$EventsCount)
#viridis
palVir <- colorNumeric(
palette = "magma",
domain = departSta$EventsCount)
#RGB or Named the colors: palette(), c("#000000", "#0000FF", "#FFFFFF"), topo.colors(10) etc
#A function that receives a single value between 0 and 1 and returns a color: colorRamp(c("#000000", "#FFFFFF"), interpolate="spline") etc
Continuous Data
To color continuous data, there are three available color functions:
colorNumeric, colorBin, and colorQuantile.
#Continuous input, continuous colors (colorNumeric)
palConC <- colorNumeric(
palette = "RdYlBu",
domain = departSta$EventsCount)
#Continuous input, discrete colors (colorBin and colorQuantile)
# colorBin:slicing the input domain up by value(bin)
palBin<-colorBin("Blues", departSta$EventsCount, 5, pretty = FALSE)
#colorQuantile: slicing the input domain into subsets with equal numbers of observations (by quantile)
palQuan <- colorQuantile("Blues", departSta$EventsCount, n = 7)
Continuous Data Example
# colorBin
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addCircles(lng = ~long,
lat = ~lat,
#stroke width in pixels
weight = 10,
#changes transparency, like alpha in ggplot
opacity = 1,
color = ~palBin(EventsCount))
# colorQuantile
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addCircles(lng = ~long,
lat = ~lat,
#stroke width in pixels
weight = 10,
#changes transparency, like alpha in ggplot
opacity = 1,
color = ~palQuan(EventsCount))
Categorical Data
For categorical data, you will use the colorFactor function. If you
want to specify the input domain, you can either by passing a factor or
character vector to domain, or by providing levels directly using the
levels parameter (in which case the domain will be ignored).
#Domain
palFacD<-colorFactor(palette = "Blues", MplsStops$problem)
#Level
palFacL<-colorFactor(topo.colors(5),levels = MplsStops$problem)
Categorical Example
leaflet(data = MplsStops) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(lng = ~long,
lat = ~lat,
weight = 1,
opacity = 1,
stroke = TRUE,
color = ~palFacL(problem))
Lines and Shape
For creating a rectangle, the four vector arguments indicating its
four angles are required.While the Polygons and Polylines are more
flexible and can be inferred from the data object.
#rectangle
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addCircles(
lng = ~ long,
lat = ~ lat,
#stroke width in pixels
weight = 10,
#changes transparency, like alpha in ggplot
opacity = 1,
color = ~ palQuan(EventsCount)
) %>%
addRectangles(
lng1 = -77.20250,
lat1 =38.80111,
lng2 =-76.93186,
lat2 = 39.12351,
fillColor = "transparent"
)
#Polygons and Polylines
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addPolygons(
lng = ~ long,
lat = ~ lat,
# set the opacity of the outline
opacity = 1,
# set the stroke width in pixels
weight = 1,
# set the fill opacity
fillOpacity = 0.6
)
Legend
addLegend() function is aware of the different types of palette
functions, and will create an appropriate default rendering for each
type.Thus, you do not need to edit it manully when changing the domain
or other scales.
leaflet(data = MplsStops) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(lng = ~long,
lat = ~lat,
weight = 1,
opacity = 1,
stroke = TRUE,
color = ~palFacL(problem)) %>%
addLegend(position = "bottomleft",
pal = palFacL,
values = ~problem,
title = "Type of Stops")
Choropleth Map
#In this case, we’ll use the geojsonio package to load the data into sp objects, which will let us easily manipulate the geographic features, and their properties, in R.
states <- geojsonio::geojson_read("https://rstudio.github.io/leaflet/json/us-states.geojson", what = "sp")
bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)
# Creating labels
labels <- sprintf(
"<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
states$name, states$density
) %>% lapply(htmltools::HTML)
leaflet(states) %>%
setView(-96, 37.8, 4) %>%
addProviderTiles("MapBox", options = providerTileOptions(
id = "mapbox.light",
accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN'))) %>%
addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
# hightlight the polygon when curse over it
highlightOptions = highlightOptions(
weight = 5,
color = "#666",
dashArray = "",
fillOpacity = 0.7,
bringToFront = TRUE),
# add labels
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto"))
LS0tDQp0aXRsZTogJ0EgR3VpZGUgdG8gTGVhZmxldCcNCmF1dGhvcjogIlBpcHBhLFJpdGEsSmVubnkiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIGtlZXBfbWQ6IFRSVUUNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGVycm9yPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCkxlYWZsZXQgaXMgb25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgb3Blbi1zb3VyY2UgSmF2YVNjcmlwdCBsaWJyYXJpZXMgZm9yIGludGVyYWN0aXZlIG1hcHMuIEl04oCZcyB1c2VkIGJ5IHdlYnNpdGVzIHJhbmdpbmcgZnJvbSBUaGUgTmV3IFlvcmsgVGltZXMgYW5kIFRoZSBXYXNoaW5ndG9uIFBvc3QgdG8gR2l0SHViIGFuZCBGbGlja3IsIGFzIHdlbGwgYXMgR0lTIHNwZWNpYWxpc3RzIGxpa2UgT3BlblN0cmVldE1hcCwgTWFwYm94LCBhbmQgQ2FydG9EQi4NCg0KVGhpcyBSIHBhY2thZ2UgbWFrZXMgaXQgZWFzeSB0byBpbnRlZ3JhdGUgYW5kIGNvbnRyb2wgTGVhZmxldCBtYXBzIGluIFIuDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpICAgICAjIGZvciBkYXRhIGNsZWFuaW5nIGFuZCBwbG90dGluZw0KbGlicmFyeShncGxvdHMpICAgICAgICAjIGZvciBjb2wyaGV4KCkgZnVuY3Rpb24NCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAgIyBmb3IgY29sb3IgcGFsZXR0ZXMNCmxpYnJhcnkoc2YpICAgICAgICAgICAgIyBmb3Igd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YQ0KbGlicmFyeShsZWFmbGV0KSAgICAgICAjIGZvciBoaWdobHkgY3VzdG9taXphYmxlIG1hcHBpbmcNCmxpYnJhcnkoY2FyRGF0YSkgICAgICAgIyBmb3IgTWlubmVhcG9saXMgcG9saWNlIHN0b3BzIGRhdGENCmxpYnJhcnkodGlkeXR1ZXNkYXlSKSAgIyBmb3IgYmlnZm9vdCBkYXRhIA0KbGlicmFyeShnZ3RoZW1lcykgICAgICAjIGZvciBtb3JlIHRoZW1lcyAoaW5jbHVkaW5nIHRoZW1lX21hcCgpKQ0KbGlicmFyeShodG1sdG9vbHMpDQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQ0KYGBgDQoNCiMjIEZlYXR1cmVzDQpJbnRlcmFjdGl2ZSBwYW5uaW5nL3pvb21pbmcNCiAgKkNvbXBvc2UgbWFwcyB1c2luZyBhcmJpdHJhcnkgY29tYmluYXRpb25zIG9mOg0KICAgICAgwrdNYXAgdGlsZXMNCiAgICAgIMK3TWFya2Vycw0KICAgICAgwrdQb2x5Z29ucw0KICAgICAgwrdMaW5lcw0KICAgICAgwrdQb3B1cHMNCiAgICAgIMK3R2VvSlNPTg0KICAqQ3JlYXRlIG1hcHMgcmlnaHQgZnJvbSB0aGUgUiBjb25zb2xlIG9yIFJTdHVkaW8NCiAgKkVtYmVkIG1hcHMgaW4ga25pdHIvUiBNYXJrZG93biBkb2N1bWVudHMgYW5kIFNoaW55IGFwcHMNCiAgKkVhc2lseSByZW5kZXIgc3BhdGlhbCBvYmplY3RzIGZyb20gdGhlIHNwIG9yIHNmIHBhY2thZ2VzLCBvciBkYXRhIGZyYW1lcyB3aXRoIGxhdGl0dWRlL2xvbmdpdHVkZSBjb2x1bW5zDQogICpVc2UgbWFwIGJvdW5kcyBhbmQgbW91c2UgZXZlbnRzIHRvIGRyaXZlIFNoaW55IGxvZ2ljDQogICpEaXNwbGF5IG1hcHMgaW4gbm9uIHNwaGVyaWNhbCBtZXJjYXRvciBwcm9qZWN0aW9ucw0KICAqQXVnbWVudCBtYXAgZmVhdHVyZXMgdXNpbmcgY2hvc2VuIHBsdWdpbnMgZnJvbSBsZWFmbGV0IHBsdWdpbnMgcmVwb3NpdG9yeQ0KDQoNCiMgQmFzZSBtYXBzDQoNCj5XZSBhcmUgZ29ubmEgc3RhcnQgZnJvbSB0aGUgYm90dG9tIGxheWVyIHVwLiBUaGUgZmlyc3QgdGhpbmcgdG8gbG9vayBpdCBpcyB0aGUgYmFzZSBtYXBzLiBUaGlzIG1lYW5zLCB3aGF0IHdpbGwgYmUgYmVoaW5kIHlvdXIgcG9pbnRzIG9yIHNoYXBlcy4gVGhlcmUgYXJlIG1hbnkgb3B0aW9ucyBhbmQgdGhleSBhcmUgc3VwZXIgZWFzeSB0byBsb2FkIGluLiANCldlIGNhbiBzdGFydCBvZmYgd2l0aCB0aGUgYmFzaWMgYmFzaWMgbWFwLCBzaW1wbGUgYmx1ZSBvY2VhbiBhbmQgd2hpdGUgbGFuZCwgaWYgeW91IHpvb20gaW4gdGhlcmUgd2lsbCBiZSBtb3JlIGRldGlhbHMNCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRUaWxlcygpDQpgYGANCg0KPldlIGNhbiBhbHNvIHN0YXJ0IHRoZSBtYXAgem9vbWVkIGluIG9uIGEgc3BlY2lmaWMgYXJlYSB1c2luZyBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGFuZCB0aGUgc2V0VmlldygpIGZ1bmN0aW9uIA0KDQo+SGVyZSBpcyBTYWludCBQYXVsIE1OLCBub3cgbG9vayB1cCB5b3VyIGhvbWV0b3duIGFuZCBwcmFjdGljZSBwdXR0aW5nIGluIHRoZSBjb29yZGluYXRlcy4gDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUgDQogIHNldFZpZXcobG5nID0gLTkzLjA5MzEyNCwgbGF0ID0gNDQuOTQ5NjQyLCB6b29tID0gMTIpDQogIA0KYGBgDQoNCmBgYHtyfQ0KaG9tZXRvd24gPC0gbGVhZmxldCgpICU+JSANCiAgc2V0VmlldyhsbmcgPSAtOTMuMDkzMTI0LCBsYXQgPSA0NC45NDk2NDIsIHpvb20gPSAxMikgJT4lIA0KICAgIGFkZFRpbGVzKCkNCiAgDQpgYGANCg0KPlRoZXJlIGFyZSBtYW55IG90aGVyIGJhc2UgbWFwcyB5b3UgY2FuIHVzZSBpbiBsZWFmbGV0ISANClRvIGdldCBkaWZmZXJlbnQgYmFzZSBtYXBzIHVzZSB0aGUgZnVuY3Rpb24gYWRkUHJvdmlkZXJUaWxlcygpLCB5b3Ugd2lsbCBuZWVkIHRvIGtub3cgdGhlIG5hbWUgb2YgdGhlIGJhc2VtYXAgDQpIZXJlIGFyZSBzb21lIGV4YW1wbGVzOiANCg0KYGBge3J9DQpob21ldG93biAlPiUgDQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgDQpgYGANCg0KYGBge3J9DQpob21ldG93biAlPiUgDQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpDQpgYGANCg0KYGBge3J9DQpob21ldG93biAlPiUgDQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyTm9MYWJlbHMpDQpgYGANCg0KPlRvIGdldCB0aGUgZnVsbCBsaXN0IG9mIGJhc2UgbWFwcyBhdmFpbGFibGUgdGhyb3VnaCB0aGlzIGZ1bmN0aW9uIGNsaWNrIGhlcmU6IGh0dHA6Ly9sZWFmbGV0LWV4dHJhcy5naXRodWIuaW8vbGVhZmxldC1wcm92aWRlcnMvcHJldmlldy9pbmRleC5odG1sIA0KDQo+SWYgeW91IHdhbnQgdG8gdXNlIGFueSBvdGhlciBvbmVzIGZyb20gdGhpcyB3ZWJzaXRlIGp1c3QgcmVwbGFjZSB3aGF0IGlzIGFmdGVyIHRoZSAkIGluIHRoZSBhZGRQcm92aWRlclRpbGVzKCkuIA0KDQo+TGV0IHNheSB5b3UgcmVhbGx5IGxpa2UgdGhlIHdhdGVyIGNvbG9yIGJhc2UgbWFwIGJ1dCB0aGVyZSBhcmVuJ3QgYW55IGxhYmVscywgeW91IGNhbiBhZGQgbGF5ZXJzIHRvIHlvdXIgYmFzZSBtYXBzLiBIZXJlIEkgaGF2ZSBsYXllcmVkIHRoZSB3YXRlciBjb2xvciB3aXRoIHRoZSBsaWdodCBncmV5IGJhc2UgbWFwIHRoYXQgaGFkIGxhYmVscy4gQXMgbG9uZyBhcyB5b3Ugc2V0IHRoZSBvbmUgb24gdG9wIHRvIGhhdmUgYSBsb3dlciBvcGFjaXR5IHlvdSBjYW4gc2VlIGJvdGghICANCg0KYGBge3J9DQpob21ldG93biAlPiUgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLA0KICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjUpKSANCiANCmBgYA0KDQo+VHJ5IGV4cGVyaW1lbnRpbmcgd2l0aCB0aGUgb3BhY2l0eSBhbmQgZGlmZmVyZW50IGxheWVyaW5nIHRlY2huaXF1ZXMgd2l0aCBiYXNlIG1hcHMgdGhhdCB5b3UgbGlrZSEgDQoNCmBgYHtyfQ0KI2hvbWV0b3duICU+JSBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRfX19fX19fX19fKSAlPiUNCiAgI2FkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJF9fX19fXywNCiAgICAjb3B0aW9ucyA9IHByb3ZpZGVyVGlsZU9wdGlvbnMob3BhY2l0eSA9IF9fXykpIA0KYGBgDQoNCg0KDQojIyBNYXJrZXJzIA0KDQo+Tm93IHdlIGhhdmUgb3VyIGJhc2UgbWFwIGZpZ3VyZWQgb3V0IHdlIGNhbiBhZGQgbWFya2Vycy4gVG8gZG8gdGhhdCwgd2Ugd2lsbCBuZWVkZWQgZGF0YS4gVGhlIGJlc3QgZGF0YSB0byB1c2UgaXMgZGF0YSB3aGVyZSB0aGUgb2JzZXJ2YXRpb25zIHNob3VsZCBoYXZlIGEgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSB2YXJpYWJsZS4gVGhpcyBtYWtlcyBmb3IgYSBzbW9vdGggcHJvY2VzcyB3aGVuIGFkZGluZyBtYXJrZXJzLiANCg0KYGBge3J9DQp0dWVzZGF0YSA8LSB0aWR5dHVlc2RheVI6OnR0X2xvYWQoJzIwMjItMDktMTMnKSAjTG9hZGluZyBpbiBkYXRhIGZyb20gYSB0aWR5IHR1ZXNkYXkgdGhhdCBoYXMgZ2VvZ3JhcGhpY2FsIHBvaW50cw0KYmlnZm9vdCA8LSB0dWVzZGF0YSRiaWdmb290DQpgYGANCg0KPkZpcnN0IGxldHMgcHJhY3RpY2UgYWRkaW5nIGFsbCB0aGUgZGF0YS4NCg0KYGBge3J9DQpsZWFmbGV0KGRhdGEgPSBiaWdmb290KSAlPiUgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLA0KICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjUpKSAgJT4lDQogIGFkZE1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlKQ0KYGBgDQoNCj5JZiBSIGlzIHJ1bm5pbmcgc2xvd2x5IG5vdyBpdHMgYmVjYXVzZSB0aGVyZSBhcmUgc28gbWFueSBkYXRhIHBvaW50cy4gSXQgaXMgYmVzdCB0byBmaWx0ZXIgb3V0IG9ic2VydmF0aW9ucyB0aGF0IHlvdSBuZWVkIGJlZm9yZSBtYWtpbmcgYSBtYXAuIA0KDQpgYGB7cn0NCmJpZ2Zvb3RzdWIgPC0gYmlnZm9vdCAlPiUgDQogIGZpbHRlcihzdGF0ZSA9PSAiQ2FsaWZvcm5pYSIpICU+JSAjTmFycm93aW5nIGRvd24gdG8gYSBzdGF0ZSwgcGljayBhbnkgc3RhdGUgeW91IHdhbnQhIA0KICBmaWx0ZXIoc2Vhc29uID09ICJTdW1tZXIiKSAjIEFsc28gb25seSBsb29raW5nIGF0IGJpZ2Zvb3Qgc2lnaHRpbmdzIGR1cmluZyBvbmUgc2Vhc29uIA0KYGBgDQoNCmBgYHtyfQ0KbGVhZmxldChkYXRhID0gYmlnZm9vdHN1YikgJT4lIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5XYXRlcmNvbG9yKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbiwNCiAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC41KSkgICU+JQ0KICBhZGRNYXJrZXJzKH5sb25naXR1ZGUsIH5sYXRpdHVkZSkNCmBgYA0KDQo+V2l0aCBsZXNzIHBvaW50cyB0aGUgc29mdHdhcmUgcnVucyBmYXIgc21vb3RoZXIuIE5vdyB3ZSBjYW4gYWRkIHNvbWUgZnVuIHRoaW5ncyEgDQoNCiMgUGxhaW4gTWFya2VycyBhbmQgUG9wdXBzDQoNCj5Ob3Qgb25seSBjYW4geW91IGFkZCBsYWJlbHMgdG8gdGhlc2UgcG9pbnRzIGJ1dCB3aXRoIGxlYWZsZXQgeW91IGNhbiBhZGQgcG9wdXBzIA0KDQpgYGB7cn0NCmxlYWZsZXQoZGF0YSA9IGJpZ2Zvb3RzdWIpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLA0KICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjUpKSAlPiUgDQogIGFkZE1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlLGxhYmVsID0gfmRhdGUsIHBvcHVwID0gfmxvY2F0aW9uX2RldGFpbHMpICMgbGFiZWwgbWVhbnMgd2hhdCB3aWxsIGFwcGVhciB3aGVuIHlvdSBob3ZlciBvdmVyIHRoZSBwb2ludCBhbmQgcG9wdXAgbWVhbnMgd2hhdCB3aWxsIGFwcGVhciB3aGVuIHlvdSBjbGljayBvbiBhIHBvaW50DQpgYGANCg0KIyBBd2Vzb21lIE1hcmtlcnMNCj4gQXdlc29tZSBtYXJrZXJzIGFsbG93IHlvdSB0byBjaGFuZ2UgdGhlIGNvbG9yIG9mIHRoZSBtYXJrZXIgZGVwZW5kZW50IG9uIGEgdmFyaWFibGUgDQoNCmBgYHtyfQ0KIyBBZnRlciBjaG9vc2luZyB0ZW1wZXJhdHVyZV9oaWdoIHRvIGJlIG15IHZhcmlhYmxlIEkgYW0gdmlzdWFsaXppbmcgSSBhbSBnb2luZyB0byBmaWx0ZXIgb3V0IGFueSBvYnNlcnZhdGlvbnMgdGhhdCBkbyBub3QgaGF2ZSBhIHZhbHVlIGZvciB0ZW1wZXJhdHVyZV9oaWdoDQpiZnRlbXAgPC0gYmlnZm9vdHN1YiAlPiUgDQogIGZpbHRlcih0ZW1wZXJhdHVyZV9oaWdoICE9ICJOQSIpDQpgYGANCg0KDQpgYGB7cn0NCg0KI1RoZW4gd2Ugd2lsbCBuZWVkIHRvIGFzc2lnbiB2YWx1ZXMgb2YgdGVtcGVyYXR1cmVfaGlnaCAgdG8gY29sb3JzIGNyZWF0aW5nIGEgZ2V0Q29sb3IgZnVuY3Rpb24gDQoNCmdldENvbG9yIDwtIGZ1bmN0aW9uKGJmdGVtcCkgew0KICBzYXBwbHkoYmZ0ZW1wJHRlbXBlcmF0dXJlX2hpZ2gsIGZ1bmN0aW9uKHRlbXBlcmF0dXJlX2hpZ2gpeyAjIENyZWF0aW5nIGEgZnVuY3Rpb24gdXNpbmcgdGhlIGJmdGVtcCBkYXRhIGFuZCB0ZW1wZXJhdHVyZV9oaWdoIGRhdGEgDQogIGlmKHRlbXBlcmF0dXJlX2hpZ2ggPD0gNjgpIHsgI2NyZWF0aW5nIGFuIGlmIGVsc2Ugc3RhdGVtZW50IHRvIG1ha2UgdGhlIG1hcmtlciBhIGNlcnRhaW4gY29sb3IgYmFzZWQgb24gdGVtcGVyYXR1cmUgDQogICAgImJsdWUiDQogIH0gZWxzZSBpZih0ZW1wZXJhdHVyZV9oaWdoID49IDY5KSB7DQogICAgInJlZCINCiAgfSBlbHNlIHsNCiAgICAiZ3JlZW4iDQogIH0gfSkNCn0NCg0KaWNvbnMgPC0gYXdlc29tZUljb25zKA0KICBpY29uID0gJ2lvcy1jbG9zZScsDQogIGljb25Db2xvciA9ICdwaW5rJywgI0NvbnRyb2xzIGNvbG9yIG9mIG1pZGRsZSB4IA0KICBsaWJyYXJ5ID0gJ2lvbicsDQogIG1hcmtlckNvbG9yID0gZ2V0Q29sb3IoYmZ0ZW1wKSAjQ2FsbHMgdGhlIGZ1bmN0aW9uIA0KKQ0KDQpsZWFmbGV0KGJmdGVtcCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uV2F0ZXJjb2xvcikgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24sDQogICAgb3B0aW9ucyA9IHByb3ZpZGVyVGlsZU9wdGlvbnMob3BhY2l0eSA9IDAuNSkpICAlPiUgDQogIGFkZEF3ZXNvbWVNYXJrZXJzKH5sb25naXR1ZGUsIH5sYXRpdHVkZSxsYWJlbCA9IH5kYXRlLCBwb3B1cCA9IH5sb2NhdGlvbl9kZXRhaWxzLCBpY29uID0gaWNvbnMpICNtYWtlIHN1cmUgdG8gc2V0IHRoZSBpY29ucw0KYGBgDQogIA0KIyBDb2xvcg0KDQo+IENvbG9ycyBpbiBsZWFmbGV0IG5lZWQgdG8gYmUgaW4g4oCcaGV44oCdIGZvcm0uIFRodXMsIGluc3RlYWQgb2YgdXNpbmcgY29sb3IgZGlyZWN0bHkgYXMgaW4gZ2dwbG90LCB3ZSB3aWxsIHVzZSBjb2xvciBmdW5jdGlvbnMgd2hpY2ggY291bGQgaGVscCB1cyBjcmVhdGUgImhleCIgY29sb3IuIFRoZSBjb2xvciBmdW5jdGlvbiByZXR1cm5zIGEgcGFsZXR0ZSBmdW5jdGlvbiB0aGF0IGNhbiBiZSBwYXNzZWQgYSB2ZWN0b3Igb2YgaW5wdXQgdmFsdWVzLCBhbmQgaXTigJlsbCByZXR1cm4gYSB2ZWN0b3Igb2YgY29sb3JzIGluICNSUkdHQkIoQUEpIGZvcm1hdC5UaGVyZSBhcmUgY3VycmVudGx5IHRocmVlIGNvbG9yIGZ1bmN0aW9ucyBmb3IgZGVhbGluZyB3aXRoIGNvbnRpbnVvdXMgaW5wdXQ6IGNvbG9yTnVtZXJpYywgY29sb3JCaW4sIGFuZCBjb2xvclF1YW50aWxlOyBhbmQgb25lIGZvciBjYXRlZ29yaWNhbCBpbnB1dCwgY29sb3JGYWN0b3IuDQoNCmBgYHtyfQ0KIyBsb2FkIGNvbnRpbnVvdXMgZGF0YXNldA0KZGF0YV9zaXRlIDwtIA0KICAiaHR0cHM6Ly93d3cubWFjYWxlc3Rlci5lZHUvfmRzaHVtYW4xL2RhdGEvMTEyLzIwMTQtUTQtVHJpcHMtSGlzdG9yeS1EYXRhLnJkcyIgDQpUcmlwcyA8LSByZWFkUkRTKGd6Y29uKHVybChkYXRhX3NpdGUpKSkNClN0YXRpb25zPC1yZWFkX2NzdigiaHR0cDovL3d3dy5tYWNhbGVzdGVyLmVkdS9+ZHNodW1hbjEvZGF0YS8xMTIvREMtU3RhdGlvbnMuY3N2IikNCg0KZGVwYXJ0U3RhIDwtIFRyaXBzICU+JQ0KICBsZWZ0X2pvaW4oU3RhdGlvbnMsIGJ5ID0gYygic3N0YXRpb24iID0gIm5hbWUiKSkgJT4lDQogIGdyb3VwX2J5KGxhdCwgbG9uZykgJT4lDQogIHN1bW1hcmlzZShFdmVudHNDb3VudCA9IG4oKSkNCmBgYA0KDQojIyBDcmVhdGUgQ29sb3IgUGFsZXR0ZQ0KDQo+ICBUbyBjcmVhdGUgYSBjb2xvciBwYWxldHRlKHBhbGV0dGUgZnVuY3Rpb24pOiB5b3UgY2FsbCAxKSB0aGUgY29sb3IgZnVuY3Rpb24gd2l0aCBjb2xvcnMgKHBhbGV0dGUpIHlvdSB3YW50IHRvIHVzZSBhbmQgMikgb3B0aW9uYWxseSwgdGhlIGRvbWFpbiAodGhlIHNwZWNpZmljIHZhcmlhYmxlIHlvdSB3YW50IHRvIGluY2x1ZGUpIHRoYXQgYXJlIGV4cGVjdGVkLiBUaGUgY29sb3IgZnVuY3Rpb24gKGkuZS4gY29sb3JOdW1lcmljKSByZXR1cm5zIGEgZnVuY3Rpb24gdGhhdCBtYXBzIGEgdmFyaWFibGXigJlzIHZhbHVlcyB0byBjb2xvcnMgaW4gdGhlIGdpdmVuIHBhbGV0dGUsIGluIG9uZSBvZiB0aGUgZXhhbXBsZXMgYmVsb3cgaXMgIkJsdWVzIi4gU28sIHBhbCgpIGlzIGEgZnVuY3Rpb24uIFdlIGNhbiB0aGVuIHVzZSB0aGF0IGZ1bmN0aW9uIGluc2lkZSBvdGhlciBmdW5jdGlvbnMgd2hlcmUgeW91IHdhbnQgY29sb3JzLiANCg0KPiBOb3RpY2UgdGhhdCB0aG91Z2ggdGhlIGRvbWFpbiBhcmd1ZW1lbnQgaXMgb3B0aW9uYWwsIGl04oCZcyBpbXBvcnRhbnQgdG8gcHJvdmlkZSBhIG5vbi1OVUxMIHZhbHVlIGZvciBkb21haW4gaWYgeW91IHVzZSBhIHBhbGV0dGUgZnVuY3Rpb24gbXVsdGlwbGUgdGltZXMgYWNyb3NzIGRpZmZlcmVudCBkYXRhIHNvIHRoZSBzY2FsaW5nIGJldHdlZW4gZGF0YSBhbmQgY29sb3JzIGlzIGNvbnNpc3RlbnQuDQoNCmBgYHtyfQ0KIyBDYWxsIHRoZSBjb2xvciBmdW5jdGlvbiAoY29sb3JOdW1lcmljKSB0byBjcmVhdGUgYSBuZXcgcGFsZXR0ZSBmdW5jdGlvbg0KcGFsIDwtIGNvbG9yTnVtZXJpYyhjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIpLCAxOjEwKQ0KIyBQYXNzIHRoZSBwYWxldHRlIGZ1bmN0aW9uIGEgZGF0YSB2ZWN0b3IgdG8gZ2V0IHRoZSBjb3JyZXNwb25kaW5nIGNvbG9ycw0KcGFsKGMoMSw2LDkpKQ0KIyBjcmVhdGUgYW5vdGhlciBjb2xvciBwYWxldHRlIGZ1bmN0aW9uIHdpdGggdGhlIHJhbmdlIG9mIGlucHV0cyAoaS5lLiBkb21haW4pIA0KcGFsRG9tYWluIDwtIGNvbG9yTnVtZXJpYygNCiAgcGFsZXR0ZSA9ICJCbHVlcyIsDQogIGRvbWFpbiA9IGRlcGFydFN0YSRFdmVudHNDb3VudCkNCiMgU2hvdyB0aGUgY29ycmVzcG9uZGluZyBjb2xvcnMNCmhlYWQocGFsRG9tYWluKGRlcGFydFN0YSRFdmVudHNDb3VudCkpDQpgYGANCg0KIyMgQ29tbW9uIFBhcmFtZXRlcnMNCg0KPiBUaGUgcGFsZXR0ZSBhcmd1bWVudCBzcGVjaWZpZXMgdGhlIGNvbG9ycyB0byBtYXAgdGhlIGRhdGEuIEhlcmUgc2hvd3MgdGhlIGZvdXIgbW9zdCBjb21tb24gZm9ybXMuDQoNCi0gW1JDb2xvckJyZXdlciBdKGh0dHBzOi8vci1ncmFwaC1nYWxsZXJ5LmNvbS8zOC1yY29sb3JicmV3ZXJzLXBhbGV0dGVzLmh0bWwpDQotIFt2aXJpZGlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKQ0KLSBSR0Igb3IgTmFtZWQgdGhlIGNvbG9ycw0KLSBBIGZ1bmN0aW9uIHRoYXQgcmVjZWl2ZXMgYSBzaW5nbGUgdmFsdWUgYmV0d2VlbiAwIGFuZCAxIGFuZCByZXR1cm5zIGEgY29sb3INCg0KYGBge3J9DQojUkNvbG9yQnJld2VyIA0KcGFsQnJlIDwtIGNvbG9yTnVtZXJpYygNCiAgcGFsZXR0ZSA9ICJSZFlsQnUiLA0KICBkb21haW4gPSBkZXBhcnRTdGEkRXZlbnRzQ291bnQpDQoNCiN2aXJpZGlzDQpwYWxWaXIgPC0gY29sb3JOdW1lcmljKA0KICBwYWxldHRlID0gIm1hZ21hIiwNCiAgZG9tYWluID0gZGVwYXJ0U3RhJEV2ZW50c0NvdW50KQ0KDQojUkdCIG9yIE5hbWVkIHRoZSBjb2xvcnM6IHBhbGV0dGUoKSwgYygiIzAwMDAwMCIsICIjMDAwMEZGIiwgIiNGRkZGRkYiKSwgdG9wby5jb2xvcnMoMTApIGV0Yw0KDQojQSBmdW5jdGlvbiB0aGF0IHJlY2VpdmVzIGEgc2luZ2xlIHZhbHVlIGJldHdlZW4gMCBhbmQgMSBhbmQgcmV0dXJucyBhIGNvbG9yOiBjb2xvclJhbXAoYygiIzAwMDAwMCIsICIjRkZGRkZGIiksIGludGVycG9sYXRlPSJzcGxpbmUiKSBldGMNCmBgYA0KDQojIyBDb250aW51b3VzIERhdGENCg0KPiBUbyBjb2xvciBjb250aW51b3VzIGRhdGEsIHRoZXJlIGFyZSB0aHJlZSBhdmFpbGFibGUgY29sb3IgZnVuY3Rpb25zOiBjb2xvck51bWVyaWMsIGNvbG9yQmluLCBhbmQgY29sb3JRdWFudGlsZS4NCg0KYGBge3J9DQojQ29udGludW91cyBpbnB1dCwgY29udGludW91cyBjb2xvcnMgKGNvbG9yTnVtZXJpYykNCnBhbENvbkMgPC0gY29sb3JOdW1lcmljKA0KICBwYWxldHRlID0gIlJkWWxCdSIsDQogIGRvbWFpbiA9IGRlcGFydFN0YSRFdmVudHNDb3VudCkNCg0KI0NvbnRpbnVvdXMgaW5wdXQsIGRpc2NyZXRlIGNvbG9ycyAoY29sb3JCaW4gYW5kIGNvbG9yUXVhbnRpbGUpDQoNCiMgY29sb3JCaW46c2xpY2luZyB0aGUgaW5wdXQgZG9tYWluIHVwIGJ5IHZhbHVlKGJpbikNCnBhbEJpbjwtY29sb3JCaW4oIkJsdWVzIiwgZGVwYXJ0U3RhJEV2ZW50c0NvdW50LCA1LCBwcmV0dHkgPSBGQUxTRSkNCg0KI2NvbG9yUXVhbnRpbGU6IHNsaWNpbmcgdGhlIGlucHV0IGRvbWFpbiBpbnRvIHN1YnNldHMgd2l0aCBlcXVhbCBudW1iZXJzIG9mIG9ic2VydmF0aW9ucyAoYnkgcXVhbnRpbGUpDQpwYWxRdWFuIDwtIGNvbG9yUXVhbnRpbGUoIkJsdWVzIiwgZGVwYXJ0U3RhJEV2ZW50c0NvdW50LCBuID0gNykNCmBgYA0KDQojIyMgQ29udGludW91cyBEYXRhIEV4YW1wbGUNCg0KYGBge3J9DQojIGNvbG9yQmluDQpsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaW5lcywNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMYWJlbHMpICU+JQ0KICAgIGFkZENpcmNsZXMobG5nID0gfmxvbmcsDQogICAgICAgICAgICAgbGF0ID0gfmxhdCwNCiAgICAgICAgICAgICNzdHJva2Ugd2lkdGggaW4gcGl4ZWxzDQogICAgICAgICAgICAgd2VpZ2h0ID0gMTAsDQogICAgICAgICAgICAjY2hhbmdlcyB0cmFuc3BhcmVuY3ksIGxpa2UgYWxwaGEgaW4gZ2dwbG90DQogICAgICAgICAgICAgb3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgY29sb3IgPSB+cGFsQmluKEV2ZW50c0NvdW50KSkNCiMgY29sb3JRdWFudGlsZQ0KbGVhZmxldChkYXRhID0gZGVwYXJ0U3RhKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGluZXMsDQogICAgICAgICAgICAgICAgICAgb3B0aW9ucyA9IHByb3ZpZGVyVGlsZU9wdGlvbnMob3BhY2l0eSA9IDAuMzUpKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGFiZWxzKSAlPiUNCiAgICBhZGRDaXJjbGVzKGxuZyA9IH5sb25nLA0KICAgICAgICAgICAgIGxhdCA9IH5sYXQsDQogICAgICAgICAgICAjc3Ryb2tlIHdpZHRoIGluIHBpeGVscw0KICAgICAgICAgICAgIHdlaWdodCA9IDEwLA0KICAgICAgICAgICAgI2NoYW5nZXMgdHJhbnNwYXJlbmN5LCBsaWtlIGFscGhhIGluIGdncGxvdA0KICAgICAgICAgICAgIG9wYWNpdHkgPSAxLA0KICAgICAgICAgICAgIGNvbG9yID0gfnBhbFF1YW4oRXZlbnRzQ291bnQpKQ0KYGBgDQoNCiMjIENhdGVnb3JpY2FsIERhdGENCg0KPiBGb3IgY2F0ZWdvcmljYWwgZGF0YSwgeW91IHdpbGwgdXNlIHRoZSBjb2xvckZhY3RvciBmdW5jdGlvbi4gSWYgeW91IHdhbnQgdG8gc3BlY2lmeSB0aGUgaW5wdXQgZG9tYWluLCB5b3UgY2FuIGVpdGhlciBieSBwYXNzaW5nIGEgZmFjdG9yIG9yIGNoYXJhY3RlciB2ZWN0b3IgdG8gZG9tYWluLCBvciBieSBwcm92aWRpbmcgbGV2ZWxzIGRpcmVjdGx5IHVzaW5nIHRoZSBsZXZlbHMgcGFyYW1ldGVyIChpbiB3aGljaCBjYXNlIHRoZSBkb21haW4gd2lsbCBiZSBpZ25vcmVkKS4NCg0KYGBge3J9DQojRG9tYWluDQpwYWxGYWNEPC1jb2xvckZhY3RvcihwYWxldHRlID0gIkJsdWVzIiwgTXBsc1N0b3BzJHByb2JsZW0pDQojTGV2ZWwNCnBhbEZhY0w8LWNvbG9yRmFjdG9yKHRvcG8uY29sb3JzKDUpLGxldmVscyA9IE1wbHNTdG9wcyRwcm9ibGVtKQ0KYGBgDQoNCiMjIyBDYXRlZ29yaWNhbCBFeGFtcGxlDQoNCmBgYHtyfQ0KbGVhZmxldChkYXRhID0gTXBsc1N0b3BzKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxvbmcsDQogICAgICAgICAgICAgbGF0ID0gfmxhdCwNCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIG9wYWNpdHkgPSAxLA0KICAgICAgICAgICAgIHN0cm9rZSA9IFRSVUUsDQogICAgICAgICAgICAgY29sb3IgPSB+cGFsRmFjTChwcm9ibGVtKSkNCmBgYA0KDQojIExpbmVzIGFuZCBTaGFwZQ0KDQo+IEZvciBjcmVhdGluZyBhIHJlY3RhbmdsZSwgdGhlIGZvdXIgdmVjdG9yIGFyZ3VtZW50cyBpbmRpY2F0aW5nIGl0cyBmb3VyIGFuZ2xlcyBhcmUgcmVxdWlyZWQuV2hpbGUgdGhlIFBvbHlnb25zIGFuZCBQb2x5bGluZXMgYXJlIG1vcmUgZmxleGlibGUgYW5kIGNhbiBiZSBpbmZlcnJlZCBmcm9tIHRoZSBkYXRhIG9iamVjdC4NCg0KDQpgYGB7cn0NCiNyZWN0YW5nbGUNCmxlYWZsZXQoZGF0YSA9IGRlcGFydFN0YSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlcikgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpbmVzLA0KICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjM1KSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxhYmVscykgJT4lDQogIGFkZENpcmNsZXMoDQogICAgbG5nID0gfiBsb25nLA0KICAgIGxhdCA9IH4gbGF0LA0KICAgICNzdHJva2Ugd2lkdGggaW4gcGl4ZWxzDQogICAgd2VpZ2h0ID0gMTAsDQogICAgI2NoYW5nZXMgdHJhbnNwYXJlbmN5LCBsaWtlIGFscGhhIGluIGdncGxvdA0KICAgIG9wYWNpdHkgPSAxLA0KICAgIGNvbG9yID0gfiBwYWxRdWFuKEV2ZW50c0NvdW50KQ0KICApICU+JQ0KICBhZGRSZWN0YW5nbGVzKA0KICAgIGxuZzEgPSAtNzcuMjAyNTAsDQogICAgbGF0MSA9MzguODAxMTEsDQogICAgbG5nMiA9LTc2LjkzMTg2LA0KICAgIGxhdDIgPSAzOS4xMjM1MSwNCiAgICBmaWxsQ29sb3IgPSAidHJhbnNwYXJlbnQiDQogICkNCg0KI1BvbHlnb25zIGFuZCBQb2x5bGluZXMNCmxlYWZsZXQoZGF0YSA9IGRlcGFydFN0YSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlcikgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpbmVzLA0KICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjM1KSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxhYmVscykgJT4lDQogIGFkZFBvbHlnb25zKA0KICAgIGxuZyA9IH4gbG9uZywNCiAgICBsYXQgPSB+IGxhdCwNCiAgICAjIHNldCB0aGUgb3BhY2l0eSBvZiB0aGUgb3V0bGluZQ0KICAgIG9wYWNpdHkgPSAxLA0KICAgICMgc2V0IHRoZSBzdHJva2Ugd2lkdGggaW4gcGl4ZWxzDQogICAgd2VpZ2h0ID0gMSwNCiAgICAjIHNldCB0aGUgZmlsbCBvcGFjaXR5DQogICAgZmlsbE9wYWNpdHkgPSAwLjYNCiAgKQ0KYGBgDQoNCiMgTGVnZW5kDQoNCj4gYWRkTGVnZW5kKCkgZnVuY3Rpb24gaXMgYXdhcmUgb2YgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBwYWxldHRlIGZ1bmN0aW9ucywgYW5kIHdpbGwgY3JlYXRlIGFuIGFwcHJvcHJpYXRlIGRlZmF1bHQgcmVuZGVyaW5nIGZvciBlYWNoIHR5cGUuVGh1cywgeW91IGRvIG5vdCBuZWVkIHRvIGVkaXQgaXQgbWFudWxseSB3aGVuIGNoYW5naW5nIHRoZSBkb21haW4gb3Igb3RoZXIgc2NhbGVzLg0KDQoNCmBgYHtyIGxlZ2VuZCBleGFtcGxlfQ0KbGVhZmxldChkYXRhID0gTXBsc1N0b3BzKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxvbmcsDQogICAgICAgICAgICAgbGF0ID0gfmxhdCwNCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIG9wYWNpdHkgPSAxLA0KICAgICAgICAgICAgIHN0cm9rZSA9IFRSVUUsDQogICAgICAgICAgICAgY29sb3IgPSB+cGFsRmFjTChwcm9ibGVtKSkgJT4lIA0KICAgIGFkZExlZ2VuZChwb3NpdGlvbiA9ICJib3R0b21sZWZ0IiwgDQogICAgICAgICAgICBwYWwgPSBwYWxGYWNMLA0KICAgICAgICAgICAgdmFsdWVzID0gfnByb2JsZW0sDQogICAgICAgICAgICAgdGl0bGUgPSAiVHlwZSBvZiBTdG9wcyIpIA0KYGBgDQoNCg0KIyBDaG9yb3BsZXRoIE1hcA0KDQoNCmBgYHtyfQ0KI0luIHRoaXMgY2FzZSwgd2XigJlsbCB1c2UgdGhlIGdlb2pzb25pbyBwYWNrYWdlIHRvIGxvYWQgdGhlIGRhdGEgaW50byBzcCBvYmplY3RzLCB3aGljaCB3aWxsIGxldCB1cyBlYXNpbHkgbWFuaXB1bGF0ZSB0aGUgZ2VvZ3JhcGhpYyBmZWF0dXJlcywgYW5kIHRoZWlyIHByb3BlcnRpZXMsIGluIFIuDQoNCnN0YXRlcyA8LSBnZW9qc29uaW86Omdlb2pzb25fcmVhZCgiaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2pzb24vdXMtc3RhdGVzLmdlb2pzb24iLCB3aGF0ID0gInNwIikNCg0KYmlucyA8LSBjKDAsIDEwLCAyMCwgNTAsIDEwMCwgMjAwLCA1MDAsIDEwMDAsIEluZikNCnBhbCA8LSBjb2xvckJpbigiWWxPclJkIiwgZG9tYWluID0gc3RhdGVzJGRlbnNpdHksIGJpbnMgPSBiaW5zKQ0KDQojIENyZWF0aW5nIGxhYmVscw0KbGFiZWxzIDwtIHNwcmludGYoDQogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBwZW9wbGUgLyBtaTxzdXA+Mjwvc3VwPiIsDQogIHN0YXRlcyRuYW1lLCBzdGF0ZXMkZGVuc2l0eQ0KKSAlPiUgbGFwcGx5KGh0bWx0b29sczo6SFRNTCkNCg0KbGVhZmxldChzdGF0ZXMpICU+JQ0KICBzZXRWaWV3KC05NiwgMzcuOCwgNCkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMoIk1hcEJveCIsIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKA0KICAgIGlkID0gIm1hcGJveC5saWdodCIsDQogICAgYWNjZXNzVG9rZW4gPSBTeXMuZ2V0ZW52KCdNQVBCT1hfQUNDRVNTX1RPS0VOJykpKSAlPiUgDQogIGFkZFBvbHlnb25zKA0KICAgIGZpbGxDb2xvciA9IH5wYWwoZGVuc2l0eSksDQogICAgd2VpZ2h0ID0gMiwNCiAgICBvcGFjaXR5ID0gMSwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgZGFzaEFycmF5ID0gIjMiLA0KICAgIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgICMgaGlnaHRsaWdodCB0aGUgcG9seWdvbiB3aGVuIGN1cnNlIG92ZXIgaXQNCiAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucygNCiAgICAgIHdlaWdodCA9IDUsDQogICAgICBjb2xvciA9ICIjNjY2IiwNCiAgICAgIGRhc2hBcnJheSA9ICIiLA0KICAgICAgZmlsbE9wYWNpdHkgPSAwLjcsDQogICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwNCiAgICAjIGFkZCBsYWJlbHMNCiAgICBsYWJlbCA9IGxhYmVscywNCiAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoDQogICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwNCiAgICAgIHRleHRzaXplID0gIjE1cHgiLA0KICAgICAgZGlyZWN0aW9uID0gImF1dG8iKSkNCmBgYA0K